package com.example.personal.aiagent.user.login;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import com.example.personal.aiagent.user.constant.LoginConstants;
import com.example.personal.aiagent.user.constant.UserAuthority;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import com.example.personal.aiagent.common.constant.ErrorCode;
import com.example.personal.aiagent.common.model.vo.CommonEmptyResponse;
import com.example.personal.aiagent.common.model.vo.SuccessResponse;
import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        Map<String, PasswordEncoder> map = new HashMap<>();
        map.put("noop", NoOpPasswordEncoder.getInstance());
        map.put("bcrypt", new BCryptPasswordEncoder());
        auth.userDetailsService(userDetailsService).passwordEncoder(new DelegatingPasswordEncoder("bcrypt", map));
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    // 自定义 filter 交给工厂管理
    @Bean
    public LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl(LoginConstants.URL_LOGIN);// 指定认证 url
        // loginFilter.setUsernameParameter(LoginRequest.LOGIN_PARAM_USERNAME);// 指定接收json 用户名 key
        // loginFilter.setPasswordParameter(LoginRequest.LOGIN_PARAM_PASSWORD);// 指定接收json 密码 key
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setRememberMeServices(rememberMeServices()); // 设置认证成功时使用自定义rememberMeService
        // 认证成功处理
        loginFilter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
            resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            resp.setStatus(HttpStatus.OK.value());
            resp.getWriter().println(new ObjectMapper().writeValueAsString(new SuccessResponse()));
        });
        // 认证失败处理
        loginFilter.setAuthenticationFailureHandler((req, resp, ex) -> {
            resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            if (ex instanceof AuthenticationServiceException) {
                if (ex.getMessage().contains("param")) {
                    resp.setStatus(HttpStatus.BAD_REQUEST.value());
                    resp.getWriter().println(new ObjectMapper().writeValueAsString(new CommonEmptyResponse(
                        ErrorCode.PARAM_ERROR)));
                    return;
                }
            }
            resp.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            resp.getWriter().println(new ObjectMapper().writeValueAsString(new CommonEmptyResponse(
                ErrorCode.LOGIN_USERNAME_PASSWORD_MISMATCH)));
        });
        return loginFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
            .antMatchers("/health/noauth").permitAll()
            .antMatchers("/user/**").hasAnyAuthority(UserAuthority.INIT.getAuthority())
            .antMatchers("/chat/**").hasAnyAuthority(UserAuthority.ADMIN.getAuthority())
            .antMatchers("/account/**").hasAnyAuthority(UserAuthority.ADMIN.getAuthority())
            .anyRequest().authenticated()// 所有请求必须认证
            .and().rememberMe() // 开启记住我功能 cookie 进行实现 1.认证成功保存记住我 cookie 到客户端 2.只有 cookie
            // 写入客户端成功才能实现自动登录功能
            .rememberMeServices(rememberMeServices()) // 设置自动登录使用哪个 rememberMeServices
            .and().exceptionHandling().authenticationEntryPoint((req, resp, ex) -> { // 未认证响应
                resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                resp.setStatus(HttpStatus.UNAUTHORIZED.value());
                resp.getWriter().println(new ObjectMapper().writeValueAsString(new CommonEmptyResponse(
                    ErrorCode.UNAUTHORIZED)));
            }).and().logout().logoutRequestMatcher(new OrRequestMatcher(new AntPathRequestMatcher(
                LoginConstants.URL_LOGOUT, HttpMethod.POST.name()))).logoutSuccessHandler((req, resp, auth) -> {
                    resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    resp.setStatus(HttpStatus.OK.value());
                    resp.getWriter().println(new ObjectMapper().writeValueAsString(new SuccessResponse()));
                }).and().csrf().disable();

        // at: 用来某个 filter 替换过滤器链中哪个 filter
        // before: 放在过滤器链中哪个 filter 之前
        // after: 放在过滤器链中那个 filter 之后
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public RememberMeServices rememberMeServices() {
        return new MyPersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), userDetailsService(),
            new InMemoryTokenRepositoryImpl());
    }
}
